/* 
 * 	This file is part of VPN Monitor Project 7..

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package monitor.gui;

import java.util.Arrays;
import java.util.LinkedList;

import monitor.interfaces.BufferDataPackage;
import monitor.interfaces.TrafficBufferPackage;
import monitor.interfaces.EnumTypes.Keys;
import monitor.interfaces.EnumTypes.Types;
import monitor.interfaces.observers.TrafficBufferObserver;

/**
 * Support class to the Diagram class, feeds data that should be plotted
 * by the diagram. Also created the marks for the vertical and horizontal
 * axis as well as the unit for these. Both vertical and horizontal marks
 * are limited to 10.
 */
class DiagramData implements TrafficBufferObserver{
	private final static int HORIZONTAL_SCALE_COUNT = 10, VERTICAL_SCALE_COUNT=10;
	private int[] largest = null, download = null, upload = null, total = null;
	private String[] time = null;
	private Diagram diagram = null;
	
	/**
	 * Construct and DiagramData for a specific diagram,
	 * it also register it self to receive the traffic buffer
	 * using observer pattern specified in 'TrafficBufferObserver'.
	 * 
	 * @param control Used for register this observer.
	 * @param diagram The diagram that this data class should be connected to.
	 */
	DiagramData(GuiControl control, Diagram diagram){
		control.addTrafficUtilityObserver(this);
		this.diagram = diagram;
		largest = new int[Keys.values().length];
		Arrays.fill(largest, 0);
	}

	/**
	 * When receiving a traffic buffer update will this method
	 * update the vertical scale, it will as well store the new
	 * data in 'trafficBuffer' to internal data structures when doing
	 * this will the data be converted to kB.
	 */
	@Override
	public void utilityUpdated(TrafficBufferPackage[] trafficBuffer) {
		BufferDataPackage[] data = trafficBuffer[0].getBuffer();
		LinkedList<String> time = new LinkedList<String>();
		double step = (data.length < HORIZONTAL_SCALE_COUNT) ? 1 : data.length/HORIZONTAL_SCALE_COUNT;
		for(int i=0, j=0;i<data.length;j++,i= (int) Math.round((j*step)))
			time.add( String.format("%d:%02d", data[i].getTime()[0], data[i].getTime()[1]) );
		this.time = time.toArray(this.time = new String[time.size()]);
		
		for(TrafficBufferPackage t : trafficBuffer){
			if(t.getType()==Types.DOWNLOAD){
				largest[0] = (int) (t.getLargest()/1e3);
				download = unpack(t.getBuffer());
			} else if(t.getType() == Types.UPLOAD){
				largest[1] = (int) (t.getLargest()/1e3);
				upload = unpack(t.getBuffer());
			} else {
				largest[2] = (int) (t.getLargest()/1e3);
				total = unpack(t.getBuffer());
			}
		}
		diagram.repaint();
	}
	
	/**
	 * Unpack buffer data to an array, when doing this
	 * is the data converted to kB.
	 * @param data That should be unpacked.
	 * @return Array contain the values, found in 'data'.
	 */
	private int[] unpack(BufferDataPackage[] data){
		int[] array = new int[data.length];
		for(int i=0;i<data.length;i++)
			array[i] = (int) (data[i].getValue()/1e3);
		return array;
	}
	
	/**
	 * Returns the horizontal scale.
	 * @return Array with the different marks, the last
	 * 		mark in the array will be empty.
	 */
	String[] getHorizontalScale(){
		return time;
	}
	
	/**
	 * Vertical scale that should correspond to the
	 * points plotted by the diagram.
	 * @return Array with the different marks, the last
	 * 		mark in the array will be empty.
	 */
	String[] getVerticalScale(){
		double step=0;
		if(largest[2] >= 1e6) //GB
			step = largest[2] / (1e6 * VERTICAL_SCALE_COUNT);
		else if(largest[2] >= 1e3) //MB
			step = largest[2] / (1e3 * VERTICAL_SCALE_COUNT);
		else  //KB
			step = largest[2] / (VERTICAL_SCALE_COUNT);
		String[] marks = new String[VERTICAL_SCALE_COUNT+1];
		for(int i=0;i<=VERTICAL_SCALE_COUNT;i++)
			marks[i] = String.format("%.02f", (i+1)*step);
		marks[marks.length-1] = "";
		return marks;
	}
	
	/**
	 * Unit for the horizontal scale. It simply returns "time".
	 * @return "time"
	 */
	String getHorizontalUnit(){
		return "time";
	}
	
	/**
	 * Vertical unit that dynamically adapt to the
	 * data plotted.
	 * @return "GB", "MB" or "KB" depending on the largest
	 * 				recorded value.
	 */
	String getVerticalUnit(){
		if(largest[2] >= 1e6) //GB
			return "GB";
		else if(largest[2] >= 1e3) //MB
			return "MB";
		else 
			return "KB";
	}
	
	/**
	 * Copy of the download array, the values in the array
	 * are in KB.
	 * @return Download array.
	 */
	int[] getDownloadData(){
		return download.clone();
	}
	
	/**
	 * Largest recorded download value in KB.
	 * @return Largest value of this type.
	 */
	int getDownloadLargest(){
		return largest[0];
	}
	
	/**
	 * Copy of the upload array, the values in the array
	 * are in KB.
	 * @return Upload array.
	 */
	int[] getUploadData(){
		return upload.clone();
	}
	
	/**
	 * Largest recorded upload value in KB.
	 * @return Largest value of this type.
	 */
	int getUploadLargest(){
		return largest[1];
	}
	
	/**
	 * Copy of the total array, the values in the array
	 * are in KB.
	 * @return Total array.
	 */
	int[] getTotalData(){
		return total.clone();
	}
	
	/**
	 * Largest recorded total value in KB.
	 * @return Largest value of this type.
	 */
	int getTotalLargest(){
		return largest[2];
	}
	
	/**
	 * Return same value as the method 'getTotalLargest'
	 * since total always should contain the largest value
	 * of the three values.
	 * @return Largest recorded value.
	 */
	int getLargest(){
		return getTotalLargest();
	}
	
	/**
	 * Whether or not data yet have been received.
	 * @return Have data been received?
	 */
	boolean dataReceived(){
		return largest[2]!=0;
	}
}
